package com.aldobucchi.x.mx.collections
{
	
	import mx.collections.ArrayCollection;
	import mx.collections.IList;
	import mx.events.CollectionEvent;
	import mx.events.CollectionEventKind;

	/**
	 * 
	 * @author aldo.bucchi@gmail.com
	 * 
	 */	
	public class ListConcatenation extends ArrayCollection
	{
		
		/**
		* will store the offsets that were used when generating the current view
		 * we need them to be able to decompose the list
		*/		
		private var childListCache:Array = new Array();
		
		private var _parentList:IList = null;
		[Bindable]
		/**
		 * 
		 * @return 
		 * 
		 */		
		public function get parentList():IList
		{
			
			return _parentList;		
		}
		public function set parentList( newParentList:IList ):void
		{
			
			fyi("parentList");
			
			var _oldParentList:IList = _parentList;
			_parentList = newParentList;
			
			// add new event listener
			addListenersToParentList( _parentList );				

			if ( _oldParentList != null )
			{
				// remove old event listener
				removeListenersFromParentList( _parentList );
			}
			else // initial load
			{
							
			}
			this.doRefresh();
		}
		

		public function ListConcatenation( parentList:IList = null )
		{
			
			fyi("ListConcatenation()");
			
			if ( parentList != null )
				this.parentList = parentList;
		}	
		
		
		/////////////////////////////////////////////////////////////////////////////////////////////////////////
		/////////////////////////////////////////// PUBLIC API //////////////////////////////////////////////////
		/////////////////////////////////////////////////////////////////////////////////////////////////////////		
		

		/**
		 * 
		 * @return an ordered array containing the index of the first item of every child list
		 * 
		 */		
		public function getChildListOffsets( ):Array
		{
			var offsets:Array = [];
			for ( var i:int=0; i<childListCache.length; i++ )
				offsets.push( childListCache[ i ].offset );	
			return offsets;	
		}
		
		
		
		public function getChildListAtIndex( index:int ):IList
		{
			
			// if we are out of bounds, return null
			if ( i < 0 || i >= this.length )
				return null;
			
			for ( var i:int=0; i<childListCache.length; i++ )
			{
				var thisEntry:Object = childListCache[i];
				if ( (i+1) < childListCache.length ) // we have a next entry
				{
					var nextEntry:Object = childListCache[ i+1 ];
					if ( thisEntry.offset <= index && index < nextEntry.offset )
						return thisEntry.childList as IList;
				}
				else // thisEntry == lastEntry
				{
					// we know we are not out of bounds, we already checked
					return ( thisEntry.childList as IList );
				}
			}
			
			return null;
		}		
		
		
		
		
		public function getOwner( item:Object ):IList
		{
			var index:int = getItemIndex( item );
			if ( index == -1 ) // sorry, looks like you're a little lost
				return null;
			return getChildListAtIndex( index );
		}
		
		
		
		
		
		/////////////////////////////////////////////////////////////////////////////////////////////////////////
		////////////////////////////////////////// LISTENERS ////////////////////////////////////////////////////
		/////////////////////////////////////////////////////////////////////////////////////////////////////////		
		
		
		
		
		private function addListenersToParentList( parentList:IList ):void
		{
			parentList.addEventListener( CollectionEvent.COLLECTION_CHANGE, handleParentListEvent );				
			for ( var i:int=0; i<parentList.length; i++ )
			{
				var childList:IList = parentList.getItemAt( i ) as IList;
				childList.addEventListener( CollectionEvent.COLLECTION_CHANGE, handleChildListEvent );
			}
		}
		private function removeListenersFromParentList( parentList:IList ):void
		{
			parentList.removeEventListener( CollectionEvent.COLLECTION_CHANGE, handleParentListEvent );				
			for ( var i:int=0; i<parentList.length; i++ )
			{
				var childList:IList = parentList.getItemAt( i ) as IList;
				childList.removeEventListener( CollectionEvent.COLLECTION_CHANGE, handleChildListEvent );
			}
		}
		
		
		
		/////////////////////////////////////////////////////////////////////////////////////////////////////////
		////////////////////////////////////////// PARENT LIST //////////////////////////////////////////////////
		/////////////////////////////////////////////////////////////////////////////////////////////////////////



		private function handleParentListEvent( event:CollectionEvent ):void
		{
			
			disableAutoUpdate();
			
			var i:int, j:int;
					
	        switch ( event.kind )
	        {
	            case CollectionEventKind.ADD:
	            
	            	var insertionPoint:uint;
	            	if ( event.location == 0 ) // it is the first list, add from index zero
	            	{
	            		insertionPoint = 0;
	            	}
	            	else // we need to figure out the offset ( look in the cache )
	            	{
		            	childListCache[ event.location - 1 ].offset as int; 
	            		// this will add it just after the previous list
	            	}
	            	
	            	for ( i=0; i<event.items.length; i++ )
	            	{
	            		
	            		var childList:IList = event.items[ i ] as IList;
	            		
	            		for ( j=0 ; j < childList.length ; j++ ) // batch add all items
	            		{
	            			addItemAt( childList.getItemAt( j ), insertionPoint ++ );
	            		}
	            		
	            		// add event listener
						childList.addEventListener( CollectionEvent.COLLECTION_CHANGE, handleChildListEvent );	            		
	            		
	            	}
	            
	            break;
	            
	            case CollectionEventKind.MOVE:
	            case CollectionEventKind.REFRESH:
	            case CollectionEventKind.REMOVE:
	            	
	            	/*
	            	for ( i=0; i<event.items.length; i++ )
	            	{
	            		var cache:Object = getCacheEntryForChildList( event.items[ i ] as IList );
	            		for ( j=cache.length ; j>=0 ; j-- )
	            			removeItemAt( cache.offset );
	            	
	            	}
	            	*/
	            	
	            	// TODO: remove event listeners to removed child lists        
	            
	            
	            case CollectionEventKind.REPLACE:
	            case CollectionEventKind.RESET:
	            case CollectionEventKind.UPDATE:
	            	
	            	doRefresh(); // refresh all for now
	            
	            break;
	        }

			

			
			updateChildListCache();
			enableAutoUpdate();
		}

		
		private function doRefresh():void
		{
			
			fyi("doRefresh()");
			
			disableAutoUpdate();
			removeAll();
			for ( var i:uint=0; i<parentList.length; i++ )
			{
				var list:IList = parentList.getItemAt( i ) as IList;			
				for ( var j:int=0; j<list.length; j++ )
					addItem( list.getItemAt( j ) );	
			}			
			updateChildListCache(); // we need to store this after every change
			enableAutoUpdate();
		}		





		/////////////////////////////////////////////////////////////////////////////////////////////////////////
		//////////////////////////////////////////// FOR EACH CHILD LIST ////////////////////////////////////////
		/////////////////////////////////////////////////////////////////////////////////////////////////////////



		private function handleChildListEvent( event:CollectionEvent ):void
		{
			disableAutoUpdate();
			
			fyi("handleChildListEvent");
			
	        switch ( event.kind )
	        {
	            case CollectionEventKind.ADD:
	            case CollectionEventKind.MOVE:
	            case CollectionEventKind.REMOVE:
	            case CollectionEventKind.REPLACE:
					// for now... not very efficient
					refreshCompleteChildList( event.target as IList );	
	            break;
	            


	            case CollectionEventKind.REFRESH:
	            case CollectionEventKind.RESET:
	            	// this is ok for refreshes and resets
					refreshCompleteChildList( event.target as IList );		            
	            break;



	            case CollectionEventKind.UPDATE:
				// should pass thru automatically ( ??? )
				break;
	            
	        }

			updateChildListCache();
			enableAutoUpdate();
		}



		/////////////////////////////////////////////////////////////////////////////////////////////////////////
		//////////////// THE CHILD LIST CACHE ( a snapshot refreshed after every change ) ///////////////////////
		/////////////////////////////////////////////////////////////////////////////////////////////////////////

		/**
		 * 
		 * It is necessary to store a snapshot of how we "look like" on the inside at this point ( after every change )
		 * because by the time we catch an event indicating a new change on any child list we won't have this info anymore.
		 * this info is also exposed by the public API so users can know, for example, which child list owns a specific item 
		 * 
		 */
		private function updateChildListCache():void
		{
			childListCache = new Array();
			for ( var i:uint=0, count:uint=0; i<parentList.length; i++ )
			{
				// note that the cache entries are ordered correctly.
				// if you change this function, don't break this order! it is assumed by other methods
				var childList:IList = parentList.getItemAt( i ) as IList;
				childListCache.push
					( {
						list:childList, 			// a reference to the child list
						offset:count,   			// offset == index of its first item
						length:childList.length		// the current length
					} );
				count += childList.length;
			}
		}

		
		//// convenience functions to access the cache ////
		
		private function getCacheEntryForChildList( childList:IList ):Object
		{
			var iof:int = indexOfInColumn( childListCache, childList, "list" );
			if ( iof == -1 )
				return null;
			return childListCache[ iof ];
		}
				
		private function getChildListLastOffset( list:IList ):int
		{
			return getCacheEntryForChildList( list ).offset as int;
		}
		
		private function getChildListLastLength(  list:IList  ):uint
		{
			return getCacheEntryForChildList( list ).length as int;
		}

		private function getChildListOffset( list:IList ):int
		{
			for ( var i:uint=0, count:uint=0; i<parentList.length; i++ )
			{
				var l:IList = parentList.getItemAt( i ) as IList;
				if ( l == list )
					return count;
				count += list.length;
			}				
			return -1;
		}
		
				

		
		
		private function refreshCompleteChildList( childList:IList ):void
		{
			disableAutoUpdate();
			
			fyi("refreshCompleteChildList()");
			
			var lastOffset:int = getChildListLastOffset( childList );
			var lastLength:int = getChildListLastLength( childList );
			var offset:int = getChildListOffset( childList );	
			var length:int = childList.length;
			
			var delta:int = length - lastLength;
			var i:int;
			
			
			if ( delta < 0 ) // remove extra slots ( we will leave just enough to fit the new values )
			{ 
				for ( i=0; i < Math.abs( delta ) ; i++ )
					removeItemAt( lastOffset );			
			}
			
				
			for ( i=0; i < length ; i++ )
			{
				if ( i < lastLength ) // we are within our limits ( step over an old item of our own list )
				{
					setItemAt( childList.getItemAt(i), lastOffset + i );
				}
				else // we exceeded our old limits, so we will stop stepping over and will start adding new items
				{
					addItemAt( childList.getItemAt(i), lastOffset + i );
				}
			}
			
			updateChildListCache();
			enableAutoUpdate();		
		}
		
		
		
		
		
		
		/////////////////////////////////////////////////////////////////////////////////////////////////////////
		////////////////////////////////////////////// UTILITIES ////////////////////////////////////////////////
		/////////////////////////////////////////////////////////////////////////////////////////////////////////
		
		
		private function sortChildListsByLastOffset( childLists:Array ):Array
		{
			var cacheCopy:Array = childListCache.concat().sortOn("offset");
			var sortedLists:Array = new Array();
			for ( var i:int=0; i<sortedLists.length; i++ )
			{
				// TODO		
			}
			return sortedLists;
		}		
		
		public function indexOfInColumn( arr:Array, value:*, columnName:String ):int
		{
		
			for ( var i:int=0; i<arr.length; i++ )
			{
				var row:Object = arr[ i ];
				if ( row.hasOwnProperty( columnName ) && ( row[ columnName ] == value ) )
						return i;
			}		
			
			return -1;		
		
		}		
		
		
		public static function fyi( msg:String ):void
		{
			// trace(msg);
		}
		
		

	}


}